package utils;

import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;

import tablaSimbolos.TS;

/**
 * Para seguir una cadena de alias se introduce la operación ref!:
 * 	fun ref!(exp, ts)
 * 		si (exp.t = ref) entonces
 * 			si (existeID(ts, exp.id)) entonces
 * 				devuelve ref!(ts[exp.id].tipo, ts)
 * 			si no
 * 				devuelve <t:err>
 * 			fsi
 * 		si no devuelve exp
 * 		fsi
 * 	ffun
 *  
 * Compatibilidad de tipos
 * -----------------------
 *	fun compatibles(e1, e2, ts)
 *		visitadas := VACIO
 *		fun compatibles2(e1, e2)
 *	si (<e1, e2> PERTENECE visitadas) entonces
 *		devuelve true
 *	si no visitadas := visitadas UNION {<e1, e2>}
 *	fsi
 *	si ((e1.t = e2.t = num) OR (e1.t = e2.t = bool)) entonces
 *		devuelve true
 *	si no si (e1.t = ref) entonces
 *		devuelve compatibles2(ts[e1.id].tipo, e2)
 *	si no si (e2.t = ref) entonces
 *		devuelve compatibles2(e1, ts[e2.id].tipo)
 *	si no si (e1.t = e2.t = array AND e1.nelems = e2.nelems) entonces
 *		devuelve compatibles2(e1.tbase, e2.tbase)
 *	si no si (e1.t = e2.t = rec AND |e1.campos| = |e2.campos|) entonces
 *		para i := 1 hasta |e1.campos| hacer
 *			si (¬compatibles2(e1.campos[i].tipo, e2.campos[i].tipo)) entonces
 *				devolver false
 *			fsi
 *		fpara
 *		devolver true
 *	si no si (e1.t = e2.t = puntero) entonces
 *		devuelve compatible2(e1.tbase, e2.tbase)
 *	si no si (e1.t = e2.t = proc AND |e1.params| = |e2.params|) entonces
 *		para i := 1 hasta |e1.params| hacer
 *			si (¬compatibles2(e1.params[i].tipo, e2.params[i].tipo) OR
 *				(e2.params[i].modo = var AND e1.params[i].modo != var)) entonces
 *				devuelve falso
 *			fsi
 *		fpara
 *		devuelve cierto
 *	si no
 *		devuelve false
 *	fsi
 *		ffun
 *		compatibles2(e1, e2)
 *	ffun
 *
 * Comparables
 * -----------
 *	fun comparables(t0, t1, op)
 *		si (op PERTENECE {'igual', 'distinto'}) entonces
 *			devuelve (t0.t = num AND t1.t = num) OR (t0.t = bool AND t1.t = bool)
 *		si no
 *			devuelve t0.t = num AND t1.t = num
 *		fsi
 *	ffun
 */
public class Info {

	protected String t;
	protected Integer tam;

	public Info(String t) {
		this.t = t;
	}

	public Info(String t, Integer tam) {
		this.t = t;
		this.tam = tam;
	}

	public String getT() {
		return t;
	}

	public void setT(String t) {
		this.t = t;
	}

	public int getTam() {
		return tam;
	}

	public void setTam(Integer tam) {
		this.tam = tam;
	}

	public Info referencia(TS ts) {
		if (t.equals("ref")) {
			InfoRef ref = (InfoRef) this;
			if (ts.existeID(ref.getId())) {
				return ts.getInfo(ref.getId()).referencia(ts);
			} else {
				return new InfoError("error");
			}
		} else {
			return this;
		}
	}

	public boolean compatibles(Info e, TS ts) {
		List<ParInfo> visitadas = new LinkedList<ParInfo>();
		return compatibles2(e, ts, visitadas);
	}

	private boolean compatibles2(Info e, TS ts, List<ParInfo> visitadas) {
		if (pertenece(e, visitadas)) {
			return true;
		} else {
			visitadas.add(new ParInfo(this, e));
		}
		if ((t.equals("int") && e.getT().equals("int"))
				|| (t.equals("boolean") && e.getT().equals("boolean"))) {
			return true;
		} else if (t.equals("ref")) {
			InfoRef ref = (InfoRef) this;
			return ts.getInfo(ref.getId()).compatibles2(e, ts, visitadas);
		} else if (e.getT().equals("ref")) {
			InfoRef ref = (InfoRef) e;
			return compatibles2(ts.getInfo(ref.getId()), ts, visitadas);
		} else if (t.equals("array") && e.getT().equals("array")
				&& ((InfoArray) this).getTam() == ((InfoArray) e).getTam()) {
			return ((InfoArray) this).gettBase().compatibles2(
					((InfoArray) e).gettBase(), ts, visitadas);
		} else if (t.equals("reg")
				&& e.getT().equals("reg")
				&& ((InfoReg) this).getCampos().size() == ((InfoReg) e)
						.getCampos().size()) {
			boolean compatibles = true;
			InfoReg reg1 = (InfoReg) this;
			InfoReg reg2 = (InfoReg) e;
			for (int i = 0; compatibles && i < reg1.getCampos().size(); i++) {
				compatibles = !reg1.getTipo(i).compatibles2(reg2.getTipo(i),
						ts, visitadas);
			}
			return compatibles;
		} else if (t.equals("puntero") && e.getT().equals("puntero")) {
			InfoPuntero punt1 = (InfoPuntero) this;
			InfoPuntero punt2 = (InfoPuntero) e;
			return punt1.gettBase().compatibles2(punt2.gettBase(), ts,
					visitadas);
		} else if (t.equals("proc")
				&& e.getT().equals("proc")
				&& ((InfoProc) this).getNParams() == ((InfoProc) e)
						.getNParams()) {
			boolean compatibles = true;
			InfoProc proc1 = (InfoProc) this;
			InfoProc proc2 = (InfoProc) e;
			for (int i = 0; compatibles && i < proc1.getNParams(); i++) {
				compatibles = !proc1.getTipo(i).compatibles2(proc2.getTipo(i),
						ts, visitadas);
			}
			return compatibles;
		} else {
			return false;
		}
	}

	public boolean comparable(Info e, String op) {
		if (op.equals("eq") || op.equals("neq") || op.equals("gt")
				|| op.equals("ge") || op.equals("lt") || op.equals("le")) {
			return t.equals("int") && e.getT().equals("int")
					|| t.equals("boolean") && e.getT().equals("boolean");
		} else {
			return t.equals("int") && e.getT().equals("int");
		}
	}

	private boolean pertenece(Info e, List<ParInfo> visitadas) {
		boolean pertenece = false;

		for (ListIterator<ParInfo> it = visitadas.listIterator(); !pertenece && it.hasNext(); ) {
			pertenece = it.next().isCorrecto(this, e);
		}

		return pertenece;
	}
	
	public boolean equals(Object o) {
		if (o == null || !(o instanceof Info)) {
			return false;
		} else {
			return t.equals(((Info) o).getT());
		}
	}
	
	public String toString() {
		return "<t: " + t + ", tam: " + tam + ">";
	}

	private class ParInfo {
		Info e1, e2;

		ParInfo(Info e1, Info e2) {
			this.e1 = e1;
			this.e2 = e2;
		}

		public boolean isCorrecto(Info e1, Info e2) {
			return e1.equals(this.e1) && e2.equals(this.e2);
		}
	}

}
